#include "ET++.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "TreeView.h"

#include "Class.h"
#include "Menu.h"
#include "String.h"
#include "OrdColl.h"
#include "TextItem.h"
#include "Math.h"
#include "ClipBoard.h"
#include "ET_stdio.h"

static bool treetest;

void Indent(OStream &s, int l)
{
	for (int i= 0; i<l; i++)
		s << " ";
}

//---- New ---------------------------------------------------------------------

int offset(Point p, Point a, Point b)
{
	int d, s;

	if (b.x <= p.x || p.x+a.x <= 0)
		return 0;
		
	int t= b.x*a.y-a.x*b.y;

	if (t > 0) {
		if (p.x < 0) {
			s= p.x * a.y;
			d= s/a.x-p.y;
		} else if (p.x > 0) {
			s= p.x * b.y;
			d= s/b.x-p.y;
		} else
			d= -p.y;
	} else {
		if (b.x < p.x+a.x) {
			s= (b.x-p.x) * a.y;
			d= b.y-(p.y+s/a.x);
		} else if (b.x > p.x+a.x) {
			s= (a.x+p.x) * b.y;
			d= s/b.x-(p.y+a.y);
		} else
			d= b.y-(p.y+a.y);
	}
	return Math::Max(0, d);
}

line *bridge(line *line1, Point p1, line *line2, Point p2)
{
	Point d;

	d.x= p2.x+line2->d.x-p1.x;
	if (line2->d.x == 0)
		d.y= line2->d.y;
	else
		d.y= (d.x*line2->d.y)/line2->d.x;
	line *r= new line(d, line2->link);
	line1->link= new line(Point(0, p2.y + line2->d.y - d.y - p1.y), r);
	return r;
}

void line::draw(Point pos)
{
	GrPaintLine(gHighlightColor, 1, eDefaultCap, pos, pos+d);
	if (link)
		link->draw(pos+d);
}

void polygon::draw(int h, Point pos)
{
	if (upper.head)
		upper.head->draw(Point(pos.x, pos.y));
	if (lower.head)
		lower.head->draw(Point(pos.x, pos.y+h));
}

Point polygon::bbox(int h)
{
	Point pos;
	int minx, maxx, miny, maxy;
	line *p;
	minx= maxx= miny= maxy= 0;
	for (p= upper.head; p; p= p->link) {
		pos+= p->d;
		minx= Math::Min(pos.x, minx);
		maxx= Math::Max(pos.x, maxx);
		miny= Math::Min(pos.y, miny);
		maxy= Math::Max(pos.y, maxy);
	}
	pos= Point(0, h);
	for (p= lower.head; p; p= p->link) {
		pos+= p->d;
		minx= Math::Min(pos.x, minx);
		maxx= Math::Max(pos.x, maxx);
		miny= Math::Min(pos.y, miny);
		maxy= Math::Max(pos.y, maxy);
	}
	return Point(maxx-minx, maxy-miny);
}

int polygon::loffset()
{
	int posy= 0, miny= 0;
	line *p;
	for (p= upper.head; p; p= p->link) {
		posy+= p->d.y;
		miny= Math::Min(posy, miny);
	}
	return miny;
}

int polygon::merge(polygon *c2)
{
	Point p;
	int total= 0, d;
	line *low, *up, *b;

	up= lower.head;
	low= c2->upper.head;

	while (low && up) {
		d= offset(p, low->d, up->d);
		p.y+= d;
		total+= d;
		
		if (p.x + low->d.x <= up->d.x) {
			p+= low->d;
			low= low->link;
		} else {
			p-= up->d;
			up= up->link;
		}
	}

	if (low) {
		b= bridge(upper.tail, Point(0,0), low, p);
		upper.tail= (b->link) ? c2->upper.tail : b;
		lower.tail= c2->lower.tail;
	} else {
		b= bridge(c2->lower.tail, p, up, Point(0,0));
		if (b->link == 0)
			lower.tail= b;
	}
	lower.head= c2->lower.head;

	return total;
}

int TreeNode::join()
{
	VObjectIter next(this);
	TreeNode *c= (TreeNode*) next(); // skip label
	int d, h, sum;

	c= (TreeNode*) next();
	c->GetMinSize();
	contour= c->contour;
	sum= h= c->height() + Gap().y;
	while (c= (TreeNode*) next()) {
		c->GetMinSize();
		d= contour.merge(&c->contour);
		c->offset= Point(0, d+h);
		h= c->height() + Gap().y;
		sum+= d+h;
	}
	return sum-Gap().y;
}

//---- TreeNode ----------------------------------------------------------------

NewMetaImpl0(TreeNode, CompositeVObject);

TreeNode::TreeNode(int id, SeqCollection *cp) : CompositeVObject(id, cp)
{
}

TreeNode::TreeNode(int va_(id), ...) : CompositeVObject(va_(id), (SeqCollection*)0)
{
	va_list ap;
	va_start(ap,va_(id));
	SetItems(ap);
	va_end(ap);
}

TreeNode::TreeNode(int id, va_list ap) : CompositeVObject(id, ap)
{
}

TreeView *TreeNode::GetTreeView()
{
	TreeView *tv= (TreeView*) GetView();
	if (tv == 0)
		Fatal("GetTreeView", "GetTreeView() == 0");
	return tv;
}

void TreeNode::SetOrigin(Point at)
{
	VObjectIter next(this);
	TreeNode *dip, *first;

	if (treetest) fprintf(stderr, "TreeNode::SetOrigin\n");
	VObject::SetOrigin(at);
	first= (TreeNode*) next();

	if (Layout() == eTLNew) { // eTLNew
		at.y-= contour.loffset();
		first->SetOrigin(at);

		while (dip= (TreeNode*) next()) {
			at+= dip->offset;
			dip->SetOrigin(Point(at.x, at.y + dip->contour.loffset()));
		}
		return;
	}

	if (Collapsed() || Size() == 1)
		first->SetOrigin(at);
	else {
		switch (Layout()) {
		case eTLTopDown:
			first->SetOrigin(at+Point((Width()-first->Width())/2, 0));
			at.y+= Gap().y+first->Height();
			while (dip= (TreeNode*) next()) {
				dip->SetOrigin(at);
				at.x+= dip->Width()+Gap().x;
			}
			break;
		case eTLIndented:
		case eTLLeftRight:
			if (Layout() == eTLIndented) {
				first->SetOrigin(at);
				at+= Point(Gap().x, first->Height()+Gap().y);
			} else {
				first->SetOrigin(at+Point(0, (Height()-first->Height())/2));
				at.x+= Gap().x+first->Width();
			}
			while (dip= (TreeNode*) next()) {
				dip->SetOrigin(at);
				at.y+= dip->Height()+Gap().y;
			}
			break;
		case eTLNew:
			break;
		}
	}
}

void TreeNode::SetExtent(Point e)
{
	if (treetest) fprintf(stderr, "TreeNode::SetExtent\n");
	if (Collapsed()) {
		VObject::SetExtent(e);
	} else {
		CompositeVObject::SetExtent(e);
	}
}

void TreeNode::Open(bool mode)
{
	if (IsOpen() == mode)
		return;
	CompositeVObject::Open(mode);
	if (GetTreeView())
		GetTreeView()->OpenTreeNode(this, mode);
}

void TreeNode::Collapse()
{
	VObjectIter next(this);
	VObject *vop;
	next();
	while (vop= next())
		vop->Open(Collapsed());
	InvertFlag(eTreeNodeCollapsed);
	ExtentChanged(this);
}

Metric TreeNode::GetMinSize()
{
	if (treetest) fprintf(stderr, "TreeNode::GetMinSize\n");
	if (Layout() == eTLNew) { //case eTLNew:
		At(0)->CalcExtent();
		if (Size() > 1) { // a node
			int h= join();
			int x= Gap().x;
			int p2y= (h-height())/2;
			int p1y= p2y+height()-h;
			((TreeNode*)At(1))->offset= Point(x+width(), p1y);
			contour.upper.head= new line(Point(width(), 0),
									new line(Point(x, p1y), contour.upper.head));
			contour.lower.head= new line(Point(width(), 0),
									new line(Point(x, p2y), contour.lower.head));
		} else {        // a leaf
			contour.upper.tail= new line(Point(width(),0), 0);
			contour.upper.head= contour.upper.tail;
			contour.lower.tail= new line(Point(0, -height()), 0);
			contour.lower.head= new line(Point(width(),0), contour.lower.tail);
		}
		return contentRect.extent= contour.bbox(height());
	}

	Metric m, m1, mn;
	VObjectIter next(this);
	VObject *dip, *first;

	first= next();

	if (Collapsed() || Size() == 1)
		return first->GetMinSize();
	m1= first->GetMinSize();
	switch(Layout()) {
	case eTLTopDown:
		while (dip= next()) {
			mn= dip->GetMinSize();
			m.extent.y= Math::Max(m.extent.y, mn.extent.y);
			m.extent.x+= mn.extent.x+Gap().x;
		}
		m.extent.x-= Gap().x;
		m.extent.x= Math::Max(m.extent.x, m1.extent.x);
		m.extent.y+= m1.extent.y+Gap().y;
		m.base= m.extent.y;
		break;
	case eTLIndented:
	case eTLLeftRight:
		while (dip= next()) {
			mn= dip->GetMinSize();
			m.extent.x= Math::Max(m.extent.x, mn.extent.x);
			m.extent.y+= mn.extent.y+Gap().y;
		}
		if (Layout() == eTLIndented) {
			m.extent.y+= m1.extent.y;
			m.extent.x+= Gap().x;
			m.extent.x= Math::Max(m.extent.x, m1.extent.x);
			m.base= m1.base;
		} else {
			m.extent.y-= Gap().y;
			m.extent.y= Math::Max(m.extent.y, m1.extent.y);
			m.extent.x+= m1.extent.x+Gap().x;
			m.base= m.extent.y;
		}
		break;

	case eTLNew:
		break;
	}
	return m;
}

void TreeNode::Draw(Rectangle r)
{
	VObject *sel= GetTreeView()->GetSelection(), *vop;

	if (Collapsed()) {
		vop= At(0);
		vop->DrawAll(r, vop == sel);
		GrStrokeRoundRect(contentRect, gPoint10);
	} else {
		if (ConnType() != eTCNone) {
			GrSetPenNormal();
			DrawConnections();
		}
		VObjectIter next(this);
		while (vop= next())
			vop->DrawAll(r, vop == sel);
	}
}

void TreeNode::DrawConnections()
{
	if (Size() == 1)
		return;
	VObjectIter next(this);
	VObject *dip, *first;
	Point p1, p2;
	int l;

	GrSetPenSize(0);
	first= next();

	if (Layout() == eTLLeftRight) {
		Point start= first->contentRect.E();
		while (dip= next())
			GrLine(start, dip->At(0)->contentRect.W());
		return;
	}

	switch (Layout()) {
	case eTLNew:
		break;
	case eTLIndented:
		if (ConnType() == eTCPerpendicular) {
			l= Gap().x/2;
			p1= first->contentRect.SW() + Point(l, 0);
			while (dip= next()) {
				p2= dip->contentRect.origin+Point(0, dip->Base()-5);
				GrLine(p2, p2-Point(l,0));
			}
			GrLine(p1, p2-Point(l,0));
		}
		break;
	case eTLTopDown:
		if (ConnType() == eTCPerpendicular || ConnType() == eTCDiagonal2) {
			l= ConnType() == eTCPerpendicular ? Gap().y/2 : 5;
			p1= first->contentRect.S();
			p2= p1+Point(0,l);
			GrLine(p1, p2);
			while (dip= next())
				GrLine(dip->contentRect.N(), dip->contentRect.N()-Point(0,l));
		}
		if (ConnType() == eTCDiagonal || ConnType() == eTCDiagonal2) {
			VObjectIter next(this);
			first= next();
			l= ConnType() == eTCDiagonal ? 0 : 5;
			p1= first->contentRect.S()+Point(0,l);

			while (dip= next())
				GrLine(p1, dip->contentRect.N()-Point(0,l));
		} else {
			l= Gap().y/2;
			p1= At(1)->contentRect.N()-Point(0,l);
			p2= At(Size()-1)->contentRect.N()-Point(0,l);
			GrLine(p1, p2);
		}
		break;
	case eTLLeftRight:
		if (ConnType() == eTCPerpendicular || ConnType() == eTCDiagonal2) {
			l= ConnType() == eTCPerpendicular ? Gap().x/2 : 5;
			p1= first->contentRect.E();
			p2= p1+Point(l,0);
			GrLine(p1, p2);
			while (dip= next())
				GrLine(dip->contentRect.W(), dip->contentRect.W()-Point(l,0));
		}
		if (ConnType() == eTCDiagonal || ConnType() == eTCDiagonal2) {
			VObjectIter next(this);
			first= next();
			l= ConnType() == eTCDiagonal ? 0 : 5;
			p1= first->contentRect.E()+Point(l,0);

			while (dip= next())
				GrLine(p1, dip->contentRect.W()-Point(l,0));
		} else {
			l= Gap().x/2;
			p1= At(1)->contentRect.W()-Point(l,0);
			p2= At(Size()-1)->contentRect.W()-Point(l,0);
			GrLine(p1, p2);
		}
		break;
	}
}

void TreeNode::Outline2(Point p1, Point p2)
{
	Rectangle r= NormRect(p1, p2);
	if (Collapsed())
		GrStrokeRoundRect(r, gPoint10);
	else if (Layout() == eTLIndented) {
		Point sw= At(0)->contentRect.SW()+(r.origin-contentRect.origin);
		int gap= Gap().x;

		GrLine(r.NW(), r.NE());
		GrLine(r.NE()+Point(0,1), r.SE());
#ifdef sun
		GrLine(r.SE()-Point(1,0), r.SW()+Point(gap,0));
#else
		Point pp= r.SW()+Point(gap,0);
		GrLine(r.SE()-Point(1,0), pp);
#endif
		GrLine(r.SW()+Point(gap,-1), r.NW()+Point(gap, At(0)->Height()));
		GrLine(sw+Point(gap-1,0), sw);
		GrLine(sw-Point(0,1), r.NW());
	} else
		CompositeVObject::Outline2(p1, p2);
}

void TreeNode::Highlight(HighlightState hst)
{
	At(0)->Highlight(hst);
}

Command *TreeNode::DispatchEvents(Point lp, Token &t, Clipper *vf)
{
	if (At(0)->ContainsPoint(lp))
		return VObject::DispatchEvents(lp, t, vf);
	if (Collapsed())
		return 0;
	return CompositeVObject::DispatchEvents(lp, t, vf);
}

Command *TreeNode::DoMiddleButtonDownCommand(Point, Token t, int)
{
	if (t.Flags & eFlgShiftKey)
		GetTreeView()->Promote(this);
	else
		GetTreeView()->Collapse(this);
	return gNoChanges;
}

Command *TreeNode::DoLeftButtonDownCommand(Point, Token, int cl)
{
	return GetTreeView()->GetNodeSelector(this, cl);
}

GrCursor TreeNode::GetCursor(Point lp)
{
	if (Collapsed() && At(0)->ContainsPoint(lp))
		return eCrsBoldCross;
	return CompositeVObject::GetCursor(lp);
}

void TreeNode::Export(OStream &s, int level)
{
	VObjectIter next(this);
	TreeNode *tn;

	tn= (TreeNode*) next();
	if (tn) {
		Indent(s, level);
		s << tn->AsString() NL;
		while (tn= (TreeNode*) next()) {
			if (tn->IsKindOf(TreeNode))
				tn->Export(s, level+2);
			else {
				Indent(s, level+2);
				s << tn->AsString() NL;
			}
		}
	}
}

VObject *TreeNode::Detect(BoolFun f, void *arg)
{
	if (!Collapsed()) {
		VObject *vop= CompositeVObject::Detect(f, arg);
		if (vop && !vop->IsKindOf(TreeNode))
			return vop;
	}
	return 0;
}

VObject *TreeNode::FindExpandedItem(VObject *vop)
{
	VObject *op, *found= 0;

	VObjectIter next(this);
	while ((op= next()) && !found) {
		if (op->IsKindOf(TreeNode))
			found= ((TreeNode*)op)->FindExpandedItem(vop);
		else
			found= op->FindItem(vop);
		if (Collapsed() && found)
			found= At(0);
	}
	return found;
}

//---- TreeNodeSelector ----------------------------------------------------------------

TreeNodeSelector::TreeNodeSelector(TreeNode *s, int cl)
{
	item= s;
	lastinside= FALSE;
	clicks= cl;
}

void TreeNodeSelector::TrackFeedback(Point, Point, bool)
{
	if (item && (lastinside != inside)) {
		item->At(0)->Outline(0);
		lastinside= inside;
	}
}

Command *TreeNodeSelector::TrackMouse(TrackPhase atp, Point, Point, Point np)
{
	VObject *image= item->At(0);
	inside= image->ContainsPoint(np);
	if (atp == eTrackRelease) {
		if (item && lastinside) {
			image->Outline(0);
			item->GetTreeView()->SetSelection(image);
			return item->GetTreeView()->NodeSelected(item->At(0), clicks);
		}
	}
	return this;
}

//---- TreeView ----------------------------------------------------------------

NewMetaImpl(TreeView, DialogView, (TE(layout), TE(connType), TP(tree), TP(selection)));

TreeView::TreeView(EvtHandler *eh, TreeLayout lt, TreeConnection tc)
																: DialogView(eh)
{
	connType= tc;
	layout= lt;
	gap= Point(30, 4);
	tree= oldtree= 0;
	selection= 0;
}

VObject *TreeView::DoCreateDialog()
{
	return new TextItem("root");
}

void TreeView::SetLayout(TreeLayout tl)
{
	layout= tl;
	switch (layout) {
	case eTLIndented:
		gap= Point(20, 2);
		break;
	case eTLTopDown:
		gap= Point(10, 40);
		break;
	case eTLLeftRight:
		gap= Point(30, 4);
		break;
	case eTLNew:
		break;
	}
	CalcLayout();
}

void TreeView::SetConnType(TreeConnection ct)
{
	connType= ct;
	CalcLayout();
}

void TreeView::SetTree(TreeNode *t, bool freeold)
{
	if (freeold && tree) {
		tree->FreeAll();
		delete tree;
	}
	SetDialog(tree= t);
	oldtree= 0;
}

void TreeView::SetExtent(Point e)
{
	if (GetExtent() != e)    // update fix
		ForceRedraw();
	DialogView::SetExtent(e);
}

void TreeView::Promote(TreeNode *newt)
{
	if (oldtree != 0 && newt == tree) {
		SetTree(oldtree, FALSE);
		oldtree= 0;
	} else {
		if (oldtree == 0)
			oldtree= tree;
		SetTree(newt, FALSE);
	}
	ForceRedraw();
}

void TreeView::Collapse(TreeNode *tn)
{
	tn->Collapse();
}

void TreeView::OpenTreeNode(TreeNode*, bool)
{
}

OStream &TreeView::PrintOn(OStream &s)
{
	return s << (int)layout SP << (int)connType SP << gap SP << tree SP;
}

IStream &TreeView::ReadFrom(IStream &s)
{
	s >> Enum(layout) >> Enum(connType) >> gap >> tree;
	SetTree(tree);
	return s;
}

void TreeView::Export(OStream &s)
{
	if (tree)
		tree->Export(s, 0);
}

VObject *TreeView::FindNode(VObject *gp)
{
	if (tree)
		return tree->FindExpandedItem(gp);
	return 0;
}

Command *TreeView::GetNodeSelector(TreeNode *tn, int cl)
{
	return new TreeNodeSelector(tn, cl);
}

Command *TreeView::NodeSelected(VObject* vop, int cl)
{
	int partcode= cl >= 2 ? cPartTreeDoubleSelect: cPartTreeSelect;
	Control(GetId(), partcode, (void*) vop);
	return gNoChanges;
}

Command *TreeView::DispatchEvents(Point lp, Token &t, Clipper *vf)
{
	if (dialogRoot && dialogRoot->ContainsPoint(lp))
		return dialogRoot->Input(lp, t, vf);
	return View::DispatchEvents(lp, t, vf);
}

void TreeView::SetSelection(VObject *vop)
{
	if (vop != selection) {
		if (selection)
			selection->ForceRedraw();
		selection= vop;
		if (selection)
			selection->ForceRedraw();
	}
}

Command *TreeView::DoMenuCommand(int cmd)
{
	if (cmd == cCOPY) {
		if (selection && selection->GetContainer())
			gClipBoard->SelectionToClipboard((Object*)selection->GetContainer()->AsPicture(), FALSE);
		return gNoChanges;
	}
	return DialogView::DoMenuCommand(cmd);
}

bool TreeView::HasSelection()
{
	return selection != 0;
}

VObject *TreeView::BuildTree(Object *op)
{
	VObject *g= NodeAsVObject(op);
	SeqCollection *l= new OrdCollection;
	l->Add(g);
	Iterator *it= MakeChildrenIter(op);
	if (it) {
		Iter next(it);
		while (op= next()) {
			VObject *newitem= BuildTree(op);
			if (!newitem->IsKindOf(TreeNode)) {
				SeqCollection *cp= new OrdCollection(1);
				cp->Add(newitem);
				newitem= MakeTreeNode(5, cp);
			}
			l->Add(newitem);
		}
	}
	g= MakeTreeNode(4, l);
	return g;
}

TreeNode *TreeView::MakeTreeNode(int id, SeqCollection *items)
{
	return new TreeNode(id, items);
}

VObject *TreeView::NodeAsVObject(Object *)
{
	AbstractMethod("AsVObject");
	return 0;
}

Iterator *TreeView::MakeChildrenIter(Object *)
{
	AbstractMethod("GetSubPartsIter");
	return 0;
}

void TreeView::InstallTree(Object *root)
{
	SetSelection(0);
	SetTree((TreeNode*)BuildTree(root));
}
